/**
 * Pangolin IM Server
 * autor:   yiyefangzhou24
 * email:   yiyefangzhou24@qq.com
 * data:    21/10/1
 */

#include "db_redis.h"
#include "log.h"
#include "cJSON.h"

// @ 不同库存放数据信息
// * 数据库1:客户端临时访问令牌
// * 数据库2:客户端libevent句柄
// * 数据库3:消息队列
// *
// * 注意：redis不是线程安全的，所有的读写要做成单例模型（原子操作），因为是单例的，所有外部调用的函数不可嵌套调用

redisContext *conn = NULL;
/* 增加互斥锁 */
pthread_mutex_t REDIS_MUTEX;

bool _get_value(int type , char * src , void * out);
bool _set_value(int type , void * data , char * src , char *dst);
bool _create_value(char * key , int fd , int time , char * out);
bool _redis_del_userinfo(int uid);

bool init_redis_pool(char * host , int port){
    //初始化互斥锁
    pthread_mutex_init(&REDIS_MUTEX, NULL);

    //连接数据库
    struct timeval timeout = { 1, 500000 };
    conn = redisConnectWithTimeout(host, port, timeout);
    if (conn == NULL || conn->err) {
        return false;
    }

    //清空所有数据库数据
    redisReply *reply;
    reply = redisCommand(conn,"FLUSHALL");
    if(reply == NULL) return false;
    freeReplyObject(reply);
    return true;
}

char * redis_getlasterror(){
    return conn->errstr;
}

void close_redis_pool(){
    redisFree(conn);
    pthread_mutex_destroy(&REDIS_MUTEX);
}

bool redis_save_userinfo(int uid , char * key , int fd , int time){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL){
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    } 
    freeReplyObject(reply);
    
    //格式化用户信息并插入数据库
    char user_info[128] = { 0 };
    if(_create_value(key , fd , time , user_info)){
        reply = redisCommand(conn,"SET %d %s" , uid , user_info);
        if(reply != NULL && reply->type == REDIS_REPLY_STATUS && !strcmp(reply->str , "OK")){
            ret = true;
        }
        freeReplyObject(reply);
    }
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

bool redis_get_userkey(int uid , char * key){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    reply = redisCommand(conn,"GET %d" , uid);
    if(reply != NULL && reply->type == REDIS_REPLY_STRING){
        //解析json格式，获得key
        char db_key[128] = { 0 };
        if(_get_value(VALUE_KEY , reply->str , db_key)){
            strcpy(key , db_key);
            ret = true;
        }
    }else if(reply->type == REDIS_REPLY_NIL){
        ret = true;
        strcpy(key , "");
    }else{

    }
    freeReplyObject(reply);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

int redis_get_userfd(int uid){
    pthread_mutex_lock(&REDIS_MUTEX);
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    int fd = 0;
    reply = redisCommand(conn,"GET %d" , uid);
    if(reply != NULL && reply->type == REDIS_REPLY_STRING){
        //解析json格式，获得fd
        if(_get_value(VALUE_FD , reply->str , &fd) == false){
            fd = 0;
        }
    }
    freeReplyObject(reply);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return fd;
}

bool redis_check_useronline(int uid){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    reply = redisCommand(conn,"GET %d" , uid);
    if(reply != NULL && reply->type == REDIS_REPLY_STRING){
        ret = true;
    }
    freeReplyObject(reply);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

bool redis_add_relationship(int uid1  , int uid2){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 2");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    time_t t = time(NULL);
    int timestamp = time(&t);
    reply = redisCommand(conn,"ZADD %d %d %d" , uid1 , timestamp , uid2);
    if(reply != NULL && reply->type == REDIS_REPLY_INTEGER && reply->integer > 0){      //正向关系表
        freeReplyObject(reply);
        time_t t1 = time(NULL);
        timestamp = time(&t1);
        reply = redisCommand(conn,"ZADD %d %d %d" , uid2 , timestamp , uid1);       //反向关系表
        if(reply != NULL && reply->type == REDIS_REPLY_INTEGER && reply->integer > 0){
            ret = true;
        }
        freeReplyObject(reply);
    }else{
        freeReplyObject(reply);
    }
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

bool redis_check_relationship(int uid1 , int uid2){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 2");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    reply = redisCommand(conn,"ZRANGE %d 0 -1" , uid1);
    if(reply != NULL && reply->type == REDIS_REPLY_ARRAY){
        for (int i = 0; i < reply->elements; i++) {
            //printf("%s\n",reply->element[i]->str);
            if(atoi(reply->element[i]->str) == uid2)
                ret = true;
        }
    }
    freeReplyObject(reply);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

bool redis_del_relationship(int uid1 , int uid2){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 2");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    reply = redisCommand(conn,"ZREM %d %d" , uid1 , uid2);
    if(reply != NULL && reply->type == REDIS_REPLY_INTEGER && reply->integer > 0){      //正向关系表
        freeReplyObject(reply);
        reply = redisCommand(conn,"ZREM %d %d" , uid2 , uid1);       //反向关系表
        if(reply != NULL && reply->type == REDIS_REPLY_INTEGER && reply->integer > 0){
            ret = true;
        }
        freeReplyObject(reply);
    }else{
        freeReplyObject(reply);
    }
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

bool redis_del_userinfo(int uid){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = _redis_del_userinfo(uid);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

bool redis_update_usertime(int uid , int time){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    reply = redisCommand(conn,"GET %d" , uid);
    if(reply != NULL && reply->type == REDIS_REPLY_STRING){
        //解析json格式，替换time的值
        char json_new[128] = { 0 };
        if(_set_value(VALUE_TIME , &time , reply->str , json_new)){
            redisReply *reply_insert;
            reply_insert = redisCommand(conn,"SET %d %s" , uid , json_new);
            if(reply_insert != NULL && reply_insert->type == REDIS_REPLY_STATUS && !strcmp(reply_insert->str , "OK")){
                ret = true;
            }
            freeReplyObject(reply_insert);
        }
    }
    freeReplyObject(reply);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

int redis_push_msg_queue(char *msg){
    pthread_mutex_lock(&REDIS_MUTEX);
    int ret = -1;
    redisReply *reply;
    //格式化用户信息并插入数据库 使用的redis的发布/订阅功能
    reply = redisCommand(conn,"PUBLISH msgqueue %s" ,msg);
    if(reply != NULL && reply->type == REDIS_REPLY_INTEGER){
        ret = reply->integer;
    }
    freeReplyObject(reply);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

/* 
*   @遍历查找redis数据库中用户信息表中的fd，并删除
*   
*   逻辑太复杂，需要优化
*/
bool redis_del_userinfobyfd(int fd){
    pthread_mutex_lock(&REDIS_MUTEX);
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);

    //遍历所有键
    int index = 0;
    do{
        reply = redisCommand(conn,"scan %d count 1",index);
        if(reply != NULL && reply->type == REDIS_REPLY_ARRAY){
            index = atoi(reply->element[0]->str);
            if(reply->elements == 1){
                freeReplyObject(reply);
                break;
            }
            if (reply->element[1]->type != REDIS_REPLY_ARRAY) {
                freeReplyObject(reply);
                break;
            }
            uint32_t i;
            for (i = 0; i < reply->element[1]->elements; i++) {
                //printf("i:%d,key:%s\n",i,reply->element[1]->element[i]->str);

                //获得键值
                redisReply *reply_val = redisCommand(conn,"GET %s" , reply->element[1]->element[i]->str);
                if(reply_val != NULL && reply_val->type == REDIS_REPLY_STRING){
                    int db_fd = 0;
                    if(_get_value(VALUE_FD , reply_val->str , &db_fd)){
                        //printf("db_fd:%d\n" , db_fd);
                        if(db_fd == fd){
                            //删除用户信息
                            if(_redis_del_userinfo(atoi(reply->element[1]->element[i]->str))){
                                //printf("done\n");
                                ret = true;
                            }
                            freeReplyObject(reply_val);
                            index = 0;  //结束循环
                            break;
                        }
                    }
                }
                freeReplyObject(reply_val);
            }
        }else{
            freeReplyObject(reply);
            break;
        }
        freeReplyObject(reply);
    } while (0 != index);
    pthread_mutex_unlock(&REDIS_MUTEX);
    return ret;
}

/* 
*   [内部函数] 删除数据库中用户信息。
*   该函数同时提供给redis_del_userinfo，redis_del_userinfobyfd两个外部函数使用，无需上锁
*/
bool _redis_del_userinfo(int uid){
    bool ret = false;
    redisReply *reply;
    reply = redisCommand(conn,"SELECT 1");
    if(reply == NULL) {
        pthread_mutex_unlock(&REDIS_MUTEX);
        return false;
    }
    freeReplyObject(reply);
    
    reply = redisCommand(conn,"DEL %d" , uid);
    if(reply != NULL && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1){
        ret = true;
    }
    freeReplyObject(reply);
    return ret;
}

/* 
*   从redis数据库值中取得用户项。
*   数据库中存储的json格式为"key":"key","time":1633873249,"fd":12}
*/

bool _get_value(int type , char * src , void * out){
    //解析json格式数据
    cJSON * root = cJSON_Parse(src);
    if(root == NULL){
        cJSON_Delete(root);
        LOG("redis : _get_value : cJSON_Parse error");
        return false;
    }

    if(type == VALUE_KEY){
        cJSON * json_key = cJSON_GetObjectItem(root , "key");
        if(json_key == NULL){
            cJSON_Delete(root);
            LOG("redis : _get_value  : cJSON_GetObjectItem key error");
            return false;
        }
        strcpy((char *)out , json_key->valuestring);
    }else if(type == VALUE_TIME){
        cJSON * json_time = cJSON_GetObjectItem(root , "time");
        if(json_time == NULL){
            cJSON_Delete(root);
            LOG("redis : _get_value  : cJSON_GetObjectItem time error");
            return false;
        }
        *(int *)out = json_time->valueint;
    }else if(type == VALUE_FD){
        cJSON * json_fd = cJSON_GetObjectItem(root , "fd");
        if(json_fd == NULL){
            cJSON_Delete(root);
            LOG("redis : _get_value  : cJSON_GetObjectItem fd error");
            return false;
        }
        *(int *)out = json_fd->valueint;
    }else{

    }
    cJSON_Delete(root);
    return true;
}

/* 
*   从redis数据库值中取得用户项。
*   数据库中存储的json格式为"key":"key","time":1633873249,"fd":12}
*
*   @ type 类型
*   @ data 要修改的数据
*   @ src 原字符串
*   @ dst 修改后的json字符串
*/
bool _set_value(int type , void * data , char * src , char *dst){
    //解析json格式数据
    cJSON * root = cJSON_Parse(src);
    if(root == NULL){
        cJSON_Delete(root);
        LOG("redis : _set_value : cJSON_Parse error");
        return false;
    }

    if(type == VALUE_TIME){
        cJSON * json_time = cJSON_GetObjectItem(root , "time");
        if(json_time == NULL){
            cJSON_Delete(root);
            LOG("redis : _set_value  : cJSON_GetObjectItem time error");
            return false;
        }
        cJSON_ReplaceItemInObject(root , "time" , cJSON_CreateNumber(*(int *)data));
        strcpy(dst , cJSON_PrintUnformatted(root));
    }else{

    }

    cJSON_Delete(root);
    return true;
}

/* 
*   创建插入redis数据库的值，格式为{"key":"key","time":1633873249,"fd":12}
*/
bool _create_value(char * key , int fd , int time , char * out){
    cJSON * root =  cJSON_CreateObject();
    if(root == NULL){
        cJSON_Delete(root);
        LOG("redis : _create_value : cJSON_Parse error");
        return false;
    }
    cJSON_AddItemToObject(root, "key", cJSON_CreateString(key));
    cJSON_AddItemToObject(root, "time", cJSON_CreateNumber(time));
    cJSON_AddItemToObject(root, "fd", cJSON_CreateNumber(fd));
    char * str_json = cJSON_PrintUnformatted(root);
    strcpy(out , str_json);
    cJSON_Delete(root);
    return true;
}